
#[derive(Debug)]
enum Packet {
    Literal {
        version: i64,
        #[allow(unused)]
        type_id: i64,
        value:   i64,
    },
    Operator {
        version: i64,
        type_id: i64,
        sub_packets: Vec<Box<Packet>>
    },
}

struct Parser {
    bits: Vec<char>,
    index: usize,
}

impl Parser {
    fn new(hex_str: String) -> Parser {
        let mut bits = Vec::new();
        fn expend_bits(bits: &mut Vec<char>, bin_str: &str) {
            for bin_char in bin_str.chars() {
                bits.push(bin_char);
            }
        }

        for hex_char in hex_str.chars() {
            match hex_char {
                '0' => expend_bits(&mut bits, "0000"),
                '1' => expend_bits(&mut bits, "0001"),
                '2' => expend_bits(&mut bits, "0010"),
                '3' => expend_bits(&mut bits, "0011"),
                '4' => expend_bits(&mut bits, "0100"),
                '5' => expend_bits(&mut bits, "0101"),
                '6' => expend_bits(&mut bits, "0110"),
                '7' => expend_bits(&mut bits, "0111"),
                '8' => expend_bits(&mut bits, "1000"),
                '9' => expend_bits(&mut bits, "1001"),
                'A' => expend_bits(&mut bits, "1010"),
                'B' => expend_bits(&mut bits, "1011"),
                'C' => expend_bits(&mut bits, "1100"),
                'D' => expend_bits(&mut bits, "1101"),
                'E' => expend_bits(&mut bits, "1110"),
                'F' => expend_bits(&mut bits, "1111"),
                _   => panic!("{}", hex_char),
            }
        }

        Parser { bits, index: 0 }
    }

    fn bit(&self, bias: usize) -> char {
        self.bits[self.index + bias]
    } 

    fn read_number(&mut self, length: usize) -> i64 {
        let mut binary_str = String::new();
        for i in 0..length {
            binary_str.push(self.bit(i));
        }

        self.index += length ;
        i64::from_str_radix(&binary_str, 2).unwrap()
    }

    fn read_literal_value(&mut self) -> i64 {
        let mut last = false;
        let mut binary_str = String::new();
        
        while !last {
            last = self.bit(0) == '0';
            for i in 1..=4 {
                binary_str.push(self.bit(i));
            }
            self.index += 5;
        }

        i64::from_str_radix(&binary_str, 2).unwrap()
    }

    fn read_sub_packets(&mut self) -> Vec<Box<Packet>> {
        let length_type_id = self.bit(0);
        self.index += 1;

        if length_type_id == '0' {
            let bits_length = self.read_number(15);
            let mut readed_length = 0;

            let mut packets = Vec::new();
            while readed_length < bits_length as usize {
                let pre_index = self.index;
                packets.push(Box::new(self.read_packet()));
                readed_length += (self.index - pre_index) as usize;
            }

            packets
        } else {
            let packet_count = self.read_number(11);
            let mut packets = Vec::new();
            for _ in 0..packet_count {
                packets.push(Box::new(self.read_packet()));
            }

            packets
        }
    }

    fn read_packet(&mut self) -> Packet {
        let version = self.read_number(3);
        let type_id = self.read_number(3);

        if type_id == 4 {
            Packet::Literal { version, type_id, value: self.read_literal_value() }
        } else {
            Packet::Operator { version, type_id, sub_packets: self.read_sub_packets() }
        }
    }
}

fn parse_input() -> Packet {
    let input = std::fs::read_to_string("./day16/input.txt").unwrap();
    Parser::new(input).read_packet()
}

#[allow(unused)]
fn part1(packet: &Packet) {
    fn get_version(packet: &Packet) -> i64 {
        match packet {
            Packet::Literal { version, .. } => *version,
            Packet::Operator { version, sub_packets, .. } => {
                version + sub_packets.iter().map(|p: &Box<Packet>| -> i64 {
                    get_version(p)
                }).sum::<i64>()
            }
        }
    }

    println!("Part1: {}", get_version(packet));
}

#[allow(unused)]
fn part2(packet: &Packet) {
    fn evalue(packet: &Packet) -> i64 {
        match packet {
            Packet::Literal { value, .. } => *value,
            Packet::Operator { type_id, sub_packets, .. } => {
                let values: Vec<i64> = sub_packets.iter().map(|p| evalue(p)).collect();
                match type_id {
                    0 => values.into_iter().sum(),
                    1 => values.into_iter().product(),
                    2 => values.into_iter().min().unwrap(),
                    3 => values.into_iter().max().unwrap(),
                    5 => if values[0] > values[1] { 1 } else { 0 },
                    6 => if values[0] < values[1] { 1 } else { 0 },
                    7 => if values[0] == values[1] { 1 } else { 0 },
                    _ => panic!(""),
                }
            }
        }
    }

    println!("Part2: {}", evalue(packet));
}

fn main() {
    let packet = parse_input();
    part1(&packet);
    part2(&packet);
}
